package dswork.html.select;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

import dswork.html.nodes.Element;
import dswork.html.parser.StringUtil;

/**
 * Base combining (and, or) evaluator.
 */
abstract class EvaluatorCombining extends Evaluator
{
	final ArrayList<Evaluator> evaluators;
	int num = 0;

	EvaluatorCombining()
	{
		super();
		evaluators = new ArrayList<Evaluator>();
	}

	EvaluatorCombining(Collection<Evaluator> evaluators)
	{
		this();
		this.evaluators.addAll(evaluators);
		updateNumEvaluators();
	}

	Evaluator rightMostEvaluator()
	{
		return num > 0 ? evaluators.get(num - 1) : null;
	}

	void replaceRightMostEvaluator(Evaluator replacement)
	{
		evaluators.set(num - 1, replacement);
	}

	void updateNumEvaluators()
	{
		// used so we don't need to bash on size() for every match test
		num = evaluators.size();
	}

	static final class And extends EvaluatorCombining
	{
		And(Collection<Evaluator> evaluators)
		{
			super(evaluators);
		}

		And(Evaluator... evaluators)
		{
			this(Arrays.asList(evaluators));
		}

		@Override
		public boolean matches(Element root, Element node)
		{
			for(int i = 0; i < num; i++)
			{
				Evaluator s = evaluators.get(i);
				if(!s.matches(root, node))
					return false;
			}
			return true;
		}

		@Override
		public String toString()
		{
			return StringUtil.join(evaluators, " ");
		}
	}

	static final class Or extends EvaluatorCombining
	{
		/**
		 * Create a new Or evaluator. The initial evaluators are ANDed together and used as the first clause of the OR.
		 * @param evaluators initial OR clause (these are wrapped into an AND evaluator).
		 */
		Or(Collection<Evaluator> evaluators)
		{
			super();
			if(num > 1)
				this.evaluators.add(new And(evaluators));
			else // 0 or 1
				this.evaluators.addAll(evaluators);
			updateNumEvaluators();
		}

		Or(Evaluator... evaluators)
		{
			this(Arrays.asList(evaluators));
		}

		Or()
		{
			super();
		}

		public void add(Evaluator e)
		{
			evaluators.add(e);
			updateNumEvaluators();
		}

		@Override
		public boolean matches(Element root, Element node)
		{
			for(int i = 0; i < num; i++)
			{
				Evaluator s = evaluators.get(i);
				if(s.matches(root, node))
					return true;
			}
			return false;
		}

		@Override
		public String toString()
		{
			return String.format(":or%s", evaluators);
		}
	}
}
